home *** CD-ROM | disk | FTP | other *** search
/ Info-Mac 3 / Info_Mac_1994-01.iso / Development / Source / MultiSession 1.04 Source / Core 27⁄June⁄1993 / CDiskCache.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-10-28  |  47.6 KB  |  1,416 lines  |  [TEXT/KAHL]

  1. /* CDiskCache.c */
  2.  
  3. #include "CDiskCache.h"
  4. #include "MiscInfo.h"
  5. #include "CSack.h"
  6. #include "Error.h"
  7. #include "WatchCursor.h"
  8.  
  9.  
  10. #define IDLETIME (60*2)
  11. #define FILEFLUSHTHRESHHOLD (60*5)
  12. #define FREEBLOCKNODESIZE (sizeof(FreeBlockEntry) * 96)
  13.  
  14. /* minimum number of free bytes required before file is compacted */
  15. /* should be declared in MiscInfo.h */
  16. #define MinFreeSpace (DiskCacheMinFreeSpace)
  17.  
  18. /* maximum percentage of total file that can be free before file is compacted */
  19. /* should be declared in MiscInfo.h */
  20. #define MaxFreeFraction (DiskCacheMaxFreeFraction)
  21.  
  22.  
  23. static CDiskCache*    LastCachePurged = NIL; /* must be generally available */
  24.  
  25. static IndexType        CDiskCache::LastIndexPurged = 0;
  26.  
  27. static MyBoolean        CDiskCache::GrowZoneInstalled = False;
  28.  
  29.  
  30. #ifdef MEMDEBUG
  31.     static void    SetDiskTag(Handle TheHandle, IndexType TheIndex)
  32.         {
  33.             char        Temp[10] = "CDC$xxxxxx";
  34.             char        Map[16] = "0123456789abcdef";
  35.             short        Scan;
  36.  
  37.             for (Scan = 5; Scan >= 0; Scan -= 1)
  38.                 {
  39.                     Temp[Scan + 4] = Map[(TheIndex >> ((5-Scan)*4)) & 0x0f];
  40.                 }
  41.             SetTag(TheHandle,Temp);
  42.         }
  43. #else
  44.     #define SetDiskTag(TheHandle,TheIndex)
  45. #endif
  46.  
  47.  
  48. /* initialize the disk cache's parameters */
  49. /* */                    CDiskCache::CDiskCache()
  50.     {
  51.         CSack*        Temp;
  52.  
  53.         /* if we use the disk caches, provide an error recovery function */
  54.         SetErrorFunction(&SystemErrorFlushAllDiskCaches);
  55.         EXECUTE(DiskCacheInitFlag = False);
  56.         if (!GrowZoneInstalled)
  57.             {
  58.                 InstallGrowZone(&MyGrowZone);
  59.                 GrowZoneInstalled = True;
  60.             }
  61.         HashTableHandle = (HashTableEntry**)AllocHandle(0);
  62.         SetTag(HashTableHandle,"CDC HashTable");
  63.         TotalSize = 0;
  64.         FreeSpace = 0;
  65.         NumBlocks = 0;
  66.         MaxIndex = 0;
  67.         FileModified = False;
  68.         FreeHashEntry = NIL;
  69.         Temp = new CSack;
  70.         Temp->ISack(sizeof(FreeBlockEntry),FREEBLOCKNODESIZE);
  71.         FreeBlockList = Temp;
  72.         this->NextCache = NIL;
  73.         this->PreviousCache = NIL; /* initially isolated */
  74.     }
  75.  
  76.  
  77. /* remove this disk cache from the system */
  78. /* */                    CDiskCache::~CDiskCache()
  79.     {
  80.         long        Index;
  81.  
  82.         ERROR(!DiskCacheInitFlag,PRERR(ForceAbort,
  83.             "CDiskCache::~CDiskCache called on uninitialized object."));
  84.         if (this->NextCache != NIL)
  85.             {
  86.                 FlushAll(DoFlushHeldBlocks);  /* save all data back to disk */
  87.                 CompactFile();  /* compress file on disk */
  88.                 FlushVitalStats(); /* rewrite crucial file information */
  89.                 if (LastCachePurged == this)
  90.                     {
  91.                         LastCachePurged = this->NextCache;
  92.                     }
  93.                 this->PreviousCache->NextCache = this->NextCache;
  94.                 this->NextCache->PreviousCache = this->PreviousCache; /* delink us from list */
  95.                 if (LastCachePurged == this)
  96.                     {
  97.                         /* this happens when we are the last cache */
  98.                         LastCachePurged = NIL;
  99.                     }
  100.             }
  101.         FCloseFile(FileReference); /* close the file */
  102.         for (Index = (HandleSize((Handle)HashTableHandle) / sizeof(HashTableEntry)) - 1;
  103.             Index >= 0; Index -= 1)
  104.             {
  105.                 /* disposing of hash table entries */
  106.                 HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index]),sizeof(HashTableEntry));
  107.                 ReleasePtr((Ptr)(*HashTableHandle)[Index].SlicePtr);
  108.             }
  109.         ReleaseHandle((Handle)HashTableHandle);
  110.         if (DeleteWhenFinished)
  111.             {
  112.                 FSSpec        Temp;
  113.  
  114.                 Temp = DiskFile;
  115.                 FDeleteIfExistsFile(&Temp);
  116.             }
  117.         delete FreeBlockList;
  118.         DeregisterIdler(this);
  119.     }
  120.  
  121.  
  122. /* create or open a new disk cache */
  123. short        CDiskCache::IDiskCache(FSSpec* FileInfo, short Mode, OSType FileType)
  124.     {
  125.         PString        Temp;
  126.         FSSpec        LocalFSSpec;
  127.         short            VRefNum;
  128.         long            DirID;
  129.         short            Error;
  130.         short            OtherFile;
  131.         short            MyFile;
  132.         long            EOF;
  133.  
  134.         ERROR(DiskCacheInitFlag,
  135.             PRERR(ForceAbort,"CDiskCache::IDiskCache called twice for same object."));
  136.         EXECUTE(DiskCacheInitFlag=True);
  137.         switch (Mode)
  138.             {
  139.                 case CreateTemporary:
  140.                     FFindTempFolder(&VRefNum,&DirID);
  141.                     FMakeTempFileName(VRefNum,DirID,Temp);
  142.                     FMakeFSSpec(VRefNum,DirID,Temp,&LocalFSSpec);
  143.                     Error = FCreate(&LocalFSSpec,CREATORCODE,FileType);
  144.                     Error |= FOpenFile(&LocalFSSpec,&MyFile); /* preserve all disk errors */
  145.                     DiskFile = LocalFSSpec;
  146.                     DeleteWhenFinished = True;
  147.                     break;
  148.                 case OpenExisting:
  149.                     Error = FOpenFile(FileInfo,&MyFile);
  150.                     DiskFile = *FileInfo;
  151.                     DeleteWhenFinished = False;
  152.                     break;
  153.                 case CreatePermanent:
  154.                     FDeleteIfExistsFile(FileInfo);
  155.                     Error = FCreate(FileInfo,CREATORCODE,FileType);
  156.                     Error |= FOpenFile(FileInfo,&MyFile);
  157.                     DiskFile = *FileInfo;
  158.                     DeleteWhenFinished = False;
  159.                     break;
  160.                 case CopyToTemporaryAndOpen:
  161.                     FFindTempFolder(&VRefNum,&DirID);
  162.                     FMakeTempFileName(VRefNum,DirID,Temp);
  163.                     FMakeFSSpec(VRefNum,DirID,Temp,&LocalFSSpec);
  164.                     Error = FCreate(&LocalFSSpec,CREATORCODE,FileType);
  165.                     Error |= FOpenFile(&LocalFSSpec,&MyFile);
  166.                     DiskFile = LocalFSSpec;
  167.                     DeleteWhenFinished = True;
  168.                     Error |= FOpenFile(FileInfo,&OtherFile);
  169.                     Error |= FGetEOF(OtherFile,&EOF);
  170.                     FMoveData(OtherFile,0,MyFile,0,EOF);
  171.                     FCloseFile(OtherFile);
  172.                     break;
  173.                 default:
  174.                     PRERR(ForceAbort,"Illegal option passed to CDiskCache::IDiskCache.");
  175.             }
  176.         FileReference = MyFile;
  177.         if (FLastError() != noErr)
  178.             {
  179.                 SetErrorStatus();
  180.                 EXECUTE(PRERR(AllowResume,"CDiskCache::IDiskCache disk error occurred."));
  181.                 return DiskError;
  182.             }
  183.         if (!InitAll())
  184.             {
  185.                 SetErrorStatus();
  186.                 EXECUTE(PRERR(AllowResume,
  187.                     "CDiskCache::IDiskCache file corrupted error occurred."));
  188.                 return FileCorrupted;
  189.             }
  190.         /* finally, linking it into the list of other objects */
  191.         /* since the system error handler uses this list to save unsaved documents, */
  192.         /* we don't want to link it in until we know everything is OK. */
  193.         if (LastCachePurged == NIL)
  194.             {
  195.                 /* this is the first cache allocated */
  196.                 this->NextCache = this;
  197.                 this->PreviousCache = this; /* link to yourself */
  198.                 LastCachePurged = this;
  199.             }
  200.          else
  201.             {
  202.                 /* there are others to link to */
  203.                 this->NextCache = LastCachePurged;
  204.                 this->PreviousCache = LastCachePurged->PreviousCache; /* our links set */
  205.                 LastCachePurged->PreviousCache->NextCache = this;
  206.                 LastCachePurged->PreviousCache = this;  /* other links set */
  207.             }
  208.         FileModified = True;
  209.         RegisterIdler(this,IDLETIME);
  210.         return noErr;
  211.     }
  212.  
  213.  
  214. /* save file, compress it, and then duplicate it into the specified location */
  215. MyBoolean            CDiskCache::SaveAs(FSSpec* WhereToSave, ulong Creator, ulong FileType)
  216.     {
  217.         short        OtherFile;
  218.         long        EOF;
  219.  
  220.         ERROR(!DiskCacheInitFlag,
  221.             PRERR(ForceAbort,"CDiskCache::SaveAs called on uninitialized object."));
  222.         FlushAll(DontFlushHeldBlocks);
  223.         CompactFile();
  224.         FDeleteIfExistsFile(WhereToSave);
  225.         if (FCreate(WhereToSave,Creator,FileType) != noErr)
  226.             {
  227.                 EXECUTE(PRERR(AllowResume,"CDiskCache::SaveAs create file error occurred."));
  228.                 return False;
  229.             }
  230.         if (FOpenFile(WhereToSave,&OtherFile) != noErr)
  231.             {
  232.                 EXECUTE(PRERR(AllowResume,"CDiskCache::SaveAs open file error occurred."));
  233.                 return False;
  234.             }
  235.         if (FGetEOF(FileReference,&EOF))
  236.             {
  237.                 EXECUTE(PRERR(AllowResume,"CDiskCache::SaveAs couldn't get EOF position."));
  238.                 return False;
  239.             }
  240.         if (FMoveData(FileReference,0,OtherFile,0,EOF) != noErr)
  241.             {
  242.                 FCloseFile(OtherFile);
  243.                 EXECUTE(PRERR(AllowResume,"CDiskCache::SaveAs move data error occurred."));
  244.                 return False;
  245.             }
  246.         if (FCloseFile(OtherFile) != noErr)
  247.             {
  248.                 EXECUTE(PRERR(AllowResume,"CDiskCache::SaveAs close file error occurred."));
  249.                 return False;
  250.             }
  251.         return True;
  252.     }
  253.  
  254.  
  255. /* attempt to allocate a new disk object */
  256. MyBoolean            CDiskCache::DiskNew(long BlockSize, IndexType* IndexOut)
  257.     {
  258.         IndexType                Index;
  259.         long                        Offset;
  260.         register HashSliceEntry*    HSE;
  261.  
  262.         ERROR(!DiskCacheInitFlag,
  263.             PRERR(ForceAbort,"CDiskCache::DiskNew called on uninitialized object."));
  264.         Index = FindFreeHashEntry();
  265.         if (Index == UnallocatedIndex)
  266.             {
  267.                 return False; /* allocation failed */
  268.             }
  269.         if (!AllocateDiskBlock(BlockSize,&Offset,Index))
  270.             {
  271.                 SetErrorStatus();
  272.                 EXECUTE(PRERR(AllowResume,
  273.                     "CDiskCache::DiskNew allocate disk block error occurred."));
  274.                 return False;
  275.             }
  276.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  277.             sizeof(HashTableEntry));
  278.         HSE = &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]);
  279.         PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,
  280.             &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]),
  281.             sizeof(HashSliceEntry));
  282.         HSE->HandleToData = NIL;
  283.         HSE->UsedCell = True;
  284.         HSE->Changed = False;
  285.         HSE->FileOffset = Offset;
  286.         HSE->NoPurge = False;
  287.         *IndexOut = Index;
  288.         return True;
  289.     }
  290.  
  291.  
  292. /* dispose of a disk thing */
  293. void                    CDiskCache::DiskDispose(IndexType Index)
  294.     {
  295.         long                        Offset;
  296.         register HashSliceEntry*    HSE;
  297.  
  298.         ERROR(!DiskCacheInitFlag,
  299.             PRERR(ForceAbort,"CDiskCache::DiskDispose called on uninitialized object."));
  300.         ERROR((((Index/HASHSLICESIZE)*sizeof(HashTableEntry) >=
  301.             HandleSize((Handle)HashTableHandle)) || ((*HashTableHandle)[Index/HASHSLICESIZE]
  302.             .SlicePtr[Index & HASHSLICEMASK].UsedCell == False)),
  303.             PRERR(ForceAbort,"CDiskCache::DiskDispose called on nonexistent block."));
  304.  
  305.         HSE = &((*HashTableHandle)[Index/HASHSLICESIZE]
  306.             .SlicePtr[Index & HASHSLICEMASK]);
  307.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  308.             sizeof(HashTableEntry));
  309.         PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,
  310.             &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]),
  311.             sizeof(HashSliceEntry));
  312.         Offset = HSE->FileOffset;
  313.         if (HSE->HandleToData != NIL)
  314.             {
  315.                 ReleaseHandle(HSE->HandleToData);
  316.             }
  317.         ReleaseHashEntry(Index);
  318.         ReleaseDiskBlock(Offset);
  319.         if (((FreeSpace >> 5)/(TotalSize) > (long)((1 << 5)*MaxFreeFraction))
  320.             && (FreeSpace > MinFreeSpace))
  321.             {
  322.                 CompactFile();
  323.             }
  324.     }
  325.  
  326.  
  327. /* load a block from disk, if it isn't already loaded */
  328. MyBoolean            CDiskCache::DiskLoad(IndexType Index)
  329.     {
  330.         ERROR(!DiskCacheInitFlag,
  331.             PRERR(ForceAbort,"CDiskCache::DiskLoad called on uninitialized object."));
  332.         ERROR((((Index/HASHSLICESIZE)*sizeof(HashTableEntry) >=
  333.             HandleSize((Handle)HashTableHandle)) || ((*HashTableHandle)[Index/HASHSLICESIZE]
  334.             .SlicePtr[Index & HASHSLICEMASK].UsedCell == False)),
  335.             PRERR(ForceAbort,"CDiskCache::DiskLoad called on nonexistent block."));
  336.  
  337.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  338.             sizeof(HashTableEntry));
  339.         PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,
  340.             &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]),
  341.             sizeof(HashSliceEntry));
  342.         return SwapBlockIn(&((*HashTableHandle)[Index/HASHSLICESIZE]
  343.             .SlicePtr[Index & HASHSLICEMASK]));
  344.     }
  345.  
  346.  
  347. /* load an object and prevent it from being purged out */
  348. MyBoolean            CDiskCache::DiskHold(IndexType Index)
  349.     {
  350.         register HashSliceEntry*        HSE;
  351.  
  352.         ERROR(!DiskCacheInitFlag,
  353.             PRERR(ForceAbort,"CDiskCache::DiskHold called on uninitialized object."));
  354.         ERROR((((Index/HASHSLICESIZE)*sizeof(HashTableEntry) >=
  355.             HandleSize((Handle)HashTableHandle)) || ((*HashTableHandle)[Index/HASHSLICESIZE]
  356.             .SlicePtr[Index & HASHSLICEMASK].UsedCell == False)),
  357.             PRERR(ForceAbort,"CDiskCache::DiskHold called on nonexistent block."));
  358.  
  359.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  360.             sizeof(HashTableEntry));
  361.         HSE = &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]);
  362.         PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,HSE,sizeof(HashSliceEntry));
  363.         HSE->NoPurge = True;
  364.         return SwapBlockIn(HSE); /* SwapBlockIn checks NoPurge and Changed flags */
  365.     }
  366.  
  367.  
  368. /* allow an object to be purged out */
  369. void                    CDiskCache::DiskUnhold(IndexType Index)
  370.     {
  371.         register HashSliceEntry*    HSE;
  372.  
  373.         ERROR(!DiskCacheInitFlag,
  374.             PRERR(ForceAbort,"CDiskCache::DiskUnhold called on uninitialized object."));
  375.         ERROR((((Index/HASHSLICESIZE)*sizeof(HashTableEntry) >=
  376.             HandleSize((Handle)HashTableHandle)) || ((*HashTableHandle)[Index/HASHSLICESIZE]
  377.             .SlicePtr[Index & HASHSLICEMASK].UsedCell == False)),
  378.             PRERR(ForceAbort,"CDiskCache::DiskUnhold called on nonexistent block."));
  379.  
  380.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  381.             sizeof(HashTableEntry));
  382.         HSE = &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]);
  383.         PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,HSE,sizeof(HashSliceEntry));
  384.         HSE->NoPurge = False;
  385.         SwapBlockIn(HSE); /* block's already in, this just checks NoPurge & Changed */
  386.     }
  387.  
  388.  
  389. /* purge an object */
  390. void                    CDiskCache::DiskUnload(IndexType Index)
  391.     {
  392.         ERROR(!DiskCacheInitFlag,
  393.             PRERR(ForceAbort,"CDiskCache::DiskUnload called on uninitialized object."));
  394.         ERROR((((Index/HASHSLICESIZE)*sizeof(HashTableEntry) >=
  395.             HandleSize((Handle)HashTableHandle)) || ((*HashTableHandle)[Index/HASHSLICESIZE]
  396.             .SlicePtr[Index & HASHSLICEMASK].UsedCell == False)),
  397.             PRERR(ForceAbort,"CDiskCache::DiskUnload called on nonexistent block."));
  398.  
  399.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  400.             sizeof(HashTableEntry));
  401.         PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,
  402.             &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]),
  403.             sizeof(HashSliceEntry));
  404.         SwapBlockOut(&((*HashTableHandle)[Index/HASHSLICESIZE]
  405.             .SlicePtr[Index & HASHSLICEMASK]));
  406.     }
  407.  
  408.  
  409. /* change the size of an object */
  410. MyBoolean            CDiskCache::DiskResize(long NewBlockSize, IndexType Index)
  411.     {
  412.         long                        NewFileOffset;
  413.         FileBlockNode        BlockHeader;
  414.         register HashSliceEntry*    HSE;
  415.  
  416.         ERROR(!DiskCacheInitFlag,
  417.             PRERR(ForceAbort,"CDiskCache::DiskResize called on uninitialized object."));
  418.         ERROR((((Index/HASHSLICESIZE)*sizeof(HashTableEntry) >=
  419.             HandleSize((Handle)HashTableHandle)) || ((*HashTableHandle)[Index/HASHSLICESIZE]
  420.             .SlicePtr[Index & HASHSLICEMASK].UsedCell == False)),
  421.             PRERR(ForceAbort,"CDiskCache::DiskResize called on nonexistent block."));
  422.  
  423.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  424.             sizeof(HashTableEntry));
  425.         PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,
  426.             &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]),
  427.             sizeof(HashSliceEntry));
  428.         HSE = &((*HashTableHandle)[Index/HASHSLICESIZE]
  429.             .SlicePtr[Index & HASHSLICEMASK]);
  430.         SwapBlockOut(HSE);
  431.         FSetFilePos(FileReference,HSE->FileOffset);
  432.         FReadBlock(FileReference,(char*)&BlockHeader,sizeof(FileBlockNode));
  433.         if (!AllocateDiskBlock(NewBlockSize,&NewFileOffset,Index))
  434.             {
  435.                 SetErrorStatus();
  436.                 EXECUTE(PRERR(AllowResume,
  437.                     "CDiskCache::Resize allocate disk block error occurred."));
  438.                 return False;
  439.             }
  440.         FMoveData(FileReference,HSE->FileOffset + sizeof(FileBlockNode),
  441.             FileReference,NewFileOffset + sizeof(FileBlockNode),BlockHeader.Size
  442.             - BlockHeader.Correction - sizeof(FileBlockNode));
  443.         ReleaseDiskBlock(HSE->FileOffset);
  444.         HSE->FileOffset = NewFileOffset;
  445.         return True;
  446.     }
  447.  
  448.  
  449. /* mark an index so that it should be saved to disk */
  450. void                    CDiskCache::DiskChanged(IndexType Index)
  451.     {
  452.         register HashSliceEntry*    HSE;
  453.  
  454.         ERROR(!DiskCacheInitFlag,
  455.             PRERR(ForceAbort,"CDiskCache::DiskChanged called on uninitialized object."));
  456.         ERROR((((Index/HASHSLICESIZE)*sizeof(HashTableEntry) >=
  457.             HandleSize((Handle)HashTableHandle)) || ((*HashTableHandle)[Index/HASHSLICESIZE]
  458.             .SlicePtr[Index & HASHSLICEMASK].UsedCell == False)),
  459.             PRERR(ForceAbort,"CDiskCache::DiskChanged called on nonexistent block."));
  460.  
  461.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  462.             sizeof(HashTableEntry));
  463.         PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,
  464.             &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]),
  465.             sizeof(HashSliceEntry));
  466.         HSE = &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]);
  467.         ERROR(HSE->HandleToData == NIL,
  468.             PRERR(AllowResume,"CDiskCache::DiskChanged called on an unloaded block."));
  469.         HSE->Changed = True;
  470.         SwapBlockIn(HSE); /* just adjusts the handle's NoPurge status */
  471.     }
  472.  
  473.  
  474. /* save a block to disk, but don't unload it */
  475. /* saves ONLY if changed flag is set */
  476. void                    CDiskCache::DiskSave(IndexType Index)
  477.     {
  478.         ERROR(!DiskCacheInitFlag,
  479.             PRERR(ForceAbort,"CDiskCache::DiskSave called on uninitialized object."));
  480.         ERROR((((Index/HASHSLICESIZE)*sizeof(HashTableEntry) >=
  481.             HandleSize((Handle)HashTableHandle)) || ((*HashTableHandle)[Index/HASHSLICESIZE]
  482.             .SlicePtr[Index & HASHSLICEMASK].UsedCell == False)),
  483.             PRERR(ForceAbort,"CDiskCache::DiskSave called on nonexistent block."));
  484.  
  485.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  486.             sizeof(HashTableEntry));
  487.         PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,
  488.             &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]),
  489.             sizeof(HashSliceEntry));
  490.         SaveBlock(&((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]));
  491.     }
  492.  
  493.  
  494. /* take a handle and make it part of the file */
  495. MyBoolean            CDiskCache::AttachHandle(Handle ObjToAttach, IndexType* IndexOut)
  496.     {
  497.         IndexType        Index;
  498.  
  499.         ERROR(!DiskCacheInitFlag,
  500.             PRERR(ForceAbort,"CDiskCache::AttachHandle called on uninitialized object."));
  501.         if (!DiskNew(HandleSize(ObjToAttach),&Index))
  502.             {
  503.                 return False;
  504.             }
  505.          else
  506.             {
  507.                 register HashSliceEntry*    HSE;
  508.  
  509.                 HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  510.                     sizeof(HashTableEntry));
  511.                 PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,
  512.                     &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]),
  513.                     sizeof(HashSliceEntry));
  514.                 HSE = &((*HashTableHandle)[Index/HASHSLICESIZE]
  515.                     .SlicePtr[Index & HASHSLICEMASK]);
  516.                 HSE->HandleToData = ObjToAttach;
  517.                 HSE->Changed = True;
  518.                 HNoPurge(ObjToAttach);
  519.                 *IndexOut = Index;
  520.                 return True;
  521.             }
  522.     }
  523.  
  524.  
  525. /* make a copy of data and return the handle to it */
  526. Handle                CDiskCache::Detach(IndexType Index)
  527.     {
  528.         Handle                    Temp;
  529.         register HashSliceEntry*    HSE;
  530.  
  531.         ERROR(!DiskCacheInitFlag,
  532.             PRERR(ForceAbort,"CDiskCache::Detach called on uninitialized object."));
  533.         ERROR((((Index/HASHSLICESIZE)*sizeof(HashTableEntry) >=
  534.             HandleSize((Handle)HashTableHandle)) || ((*HashTableHandle)[Index/HASHSLICESIZE]
  535.             .SlicePtr[Index & HASHSLICEMASK].UsedCell == False)),
  536.             PRERR(ForceAbort,"CDiskCache::Detach called on nonexistent block."));
  537.  
  538.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  539.             sizeof(HashTableEntry));
  540.         PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,
  541.             &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]),
  542.             sizeof(HashSliceEntry));
  543.         HSE = &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]);
  544.         SaveBlock(HSE); /* save changes, if any */
  545.         if (!SwapBlockIn(HSE))  /* make sure the object is loaded */
  546.             {
  547.                 return NIL;
  548.             }
  549.         Temp = HSE->HandleToData;
  550.         HSE->HandleToData = NIL;
  551.         HNoPurge(Temp);
  552.         return Temp;
  553.     }
  554.  
  555.  
  556. /* convert an index into a triple pointer */
  557. Trandle                CDiskCache::Index2Trandle(IndexType Index)
  558.     {
  559.         ERROR(!DiskCacheInitFlag,
  560.             PRERR(ForceAbort,"CDiskCache::Index2Trandle called on uninitialized object."));
  561.         ERROR((((Index/HASHSLICESIZE)*sizeof(HashTableEntry) >=
  562.             HandleSize((Handle)HashTableHandle)) || ((*HashTableHandle)[Index/HASHSLICESIZE]
  563.             .SlicePtr[Index & HASHSLICEMASK].UsedCell == False)),
  564.             PRERR(ForceAbort,"CDiskCache::Index2Trandle called on nonexistent block."));
  565.  
  566.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index/HASHSLICESIZE]),
  567.             sizeof(HashTableEntry));
  568.         PRNGCHK((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr,
  569.             &((*HashTableHandle)[Index/HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]),
  570.             sizeof(HashSliceEntry));
  571.         return &( (*HashTableHandle)[Index/HASHSLICESIZE].
  572.             SlicePtr[Index & HASHSLICEMASK].HandleToData );
  573.     }
  574.  
  575.  
  576. /* convert a triple pointer into an index */
  577. IndexType            CDiskCache::Trandle2Index(Trandle BlockRef)
  578.     {
  579.         long            Scan;
  580.         short            Index;
  581.  
  582.         ERROR(!DiskCacheInitFlag,
  583.             PRERR(ForceAbort,"CDiskCache::Trandle2Index called on uninitialized object."));
  584.         for (Scan = HandleSize((Handle)HashTableHandle) / sizeof(HashTableEntry) - 1;
  585.             Scan >= 0; Scan -= 1)
  586.             {
  587.                 HRNGCHK(HashTableHandle,&((*HashTableHandle)[Scan]),sizeof(HashTableEntry));
  588.                 PRNGCHK((*HashTableHandle)[Scan].SlicePtr,&((*HashTableHandle)[Scan]
  589.                     .SlicePtr[HASHSLICESIZE - 1]),sizeof(HashSliceEntry));
  590.                 if ((BlockRef >= &((*HashTableHandle)[Scan].SlicePtr[0].HandleToData))
  591.                     && (BlockRef <= &((*HashTableHandle)[Scan]
  592.                     .SlicePtr[HASHSLICESIZE - 1].HandleToData)))
  593.                     {
  594.                         return (((char*)BlockRef - (char*)&((*HashTableHandle)[Scan].
  595.                             SlicePtr[0].HandleToData)) / sizeof(HashSliceEntry));
  596.                     }
  597.             }
  598.         EXECUTE(PRERR(ForceAbort,"CDiskCache::Trandle2Index called on invalid Trandle."));
  599.     }
  600.  
  601.  
  602. /* convert double pointer to triple pointer */
  603. Trandle                CDiskCache::Handle2Trandle(Handle BlockHandle)
  604.     {
  605.         long        Scan;
  606.         short        Index;
  607.  
  608.         ERROR(!DiskCacheInitFlag,
  609.             PRERR(ForceAbort,"CDiskCache::Handle2Trandle called on uninitialized object."));
  610.         for (Scan = HandleSize((Handle)HashTableHandle) / sizeof(HashTableEntry) - 1;
  611.             Scan >= 0; Scan -= 1)
  612.             {
  613.                 HRNGCHK(HashTableHandle,&((*HashTableHandle)[Scan]),sizeof(HashTableEntry));
  614.                 for (Index = HASHSLICESIZE - 1; Index >= 0; Index -= 1)
  615.                     {
  616.                         PRNGCHK((*HashTableHandle)[Scan].SlicePtr,&((*HashTableHandle)[Scan]
  617.                             .SlicePtr[Index]),sizeof(HashSliceEntry));
  618.                         if ((BlockHandle == (*HashTableHandle)[Scan].SlicePtr[Index]
  619.                             .HandleToData) && ((*HashTableHandle)[Scan].SlicePtr[Index].UsedCell))
  620.                             {
  621.                                 return &((*HashTableHandle)[Scan].SlicePtr[Index].HandleToData);
  622.                             }
  623.                     }
  624.             }
  625.         EXECUTE(PRERR(ForceAbort,"CDiskCache::Handle2Trandle called on invalid handle."));
  626.     }
  627.  
  628.  
  629. IndexType            CDiskCache::Handle2Index(Handle BlockHandle)
  630.     {
  631.         long            Scan;
  632.         short            Index;
  633.  
  634.         ERROR(!DiskCacheInitFlag,
  635.             PRERR(ForceAbort,"CDiskCache::Handle2Index called on uninitialized object."));
  636.         for (Scan = HandleSize((Handle)HashTableHandle) / sizeof(HashTableEntry);
  637.             Scan >= 0; Scan -= 1)
  638.             {
  639.                 HRNGCHK(HashTableHandle,&((*HashTableHandle)[Scan]),sizeof(HashTableEntry));
  640.                 for (Index = HASHSLICESIZE - 1; Index >= 0; Index -= 1)
  641.                     {
  642.                         PRNGCHK((*HashTableHandle)[Scan].SlicePtr,&((*HashTableHandle)[Scan]
  643.                             .SlicePtr[Index]),sizeof(HashSliceEntry));
  644.                         if ((BlockHandle == (*HashTableHandle)[Scan].SlicePtr[Index]
  645.                             .HandleToData) && ((*HashTableHandle)[Scan].SlicePtr[Index].UsedCell))
  646.                             {
  647.                                 return ((Scan * HASHSLICESIZE) + Index);
  648.                             }
  649.                     }
  650.             }
  651.         EXECUTE(PRERR(ForceAbort,"CDiskCache::Handle2Index called on invalid Handle."));
  652.     }
  653.  
  654.  
  655. /* set the special index stored 4 bytes from the beginning of the file. */
  656. /* this is useful for specifying a root node of some sort */
  657. void                    CDiskCache::SetSpecialIndex(IndexType Index)
  658.     {
  659.         ERROR(!DiskCacheInitFlag,
  660.             PRERR(ForceAbort,"CDiskCache::SetSpecialIndex called on uninitialized object."));
  661.         ERROR((((Index/HASHSLICESIZE)*sizeof(HashTableEntry) >=
  662.             HandleSize((Handle)HashTableHandle)) || ((*HashTableHandle)[Index/HASHSLICESIZE]
  663.             .SlicePtr[Index & HASHSLICEMASK].UsedCell == False)),
  664.             PRERR(ForceAbort,"CDiskCache::SetSpecialIndex called on nonexistent block."));
  665.         FSetFilePos(FileReference,SPECIALINDEXOFFSET);
  666.         FWriteBlock(FileReference,(char*)&Index,sizeof(IndexType));
  667.         FFlushFile(FileReference);
  668.     }
  669.  
  670.  
  671. /* find out what the special index is */
  672. IndexType            CDiskCache::GetSpecialIndex(void)
  673.     {
  674.         IndexType        Temp;
  675.  
  676.         ERROR(!DiskCacheInitFlag,
  677.             PRERR(ForceAbort,"CDiskCache::GetSpecialIndex called on uninitialized object."));
  678.         FSetFilePos(FileReference,SPECIALINDEXOFFSET);
  679.         FReadBlock(FileReference,(char*)&Temp,sizeof(IndexType));
  680.         return Temp;
  681.     }
  682.  
  683.  
  684. /* squeeze unallocated blocks out of file */
  685. void                    CDiskCache::CompactFile(void)
  686.     {
  687.         long                    SourceScan;
  688.         long                    TargetScan;
  689.         FileBlockNode    BlockHeader;
  690.         CSack*                Temp;
  691.         long                    BlockCountTemp;
  692.  
  693.         ERROR(!DiskCacheInitFlag,
  694.             PRERR(ForceAbort,"CDiskCache::CompactFile called on uninitialized object."));
  695.         StartTimeConsumingOperation();
  696.         SourceScan = DATASTARTOFFSET;
  697.         TargetScan = SourceScan;
  698.         while (SourceScan < TotalSize)
  699.             {
  700.                 CheckCursor();
  701.                 FSetFilePos(FileReference,SourceScan);
  702.                 FReadBlock(FileReference,(char*)&BlockHeader,sizeof(FileBlockNode));
  703.                 if (BlockHeader.Index == UnallocatedIndex)
  704.                     {
  705.                         /* blank, so skip it */
  706.                         SourceScan += BlockHeader.Size;
  707.                         NumBlocks -= 1; /* we've just deleted a block */
  708.                     }
  709.                  else
  710.                     {
  711.                         /* used, so move it */
  712.                         if (SourceScan != TargetScan)
  713.                             {
  714.                                 /* why bother copying data if it's in the right place */
  715.                                 FMoveData(FileReference,SourceScan,FileReference,TargetScan,
  716.                                     BlockHeader.Size);
  717.                                 HRNGCHK(HashTableHandle,&((*HashTableHandle)[BlockHeader.Index
  718.                                     / HASHSLICESIZE]),sizeof(HashTableEntry));
  719.                                 PRNGCHK((*HashTableHandle)[BlockHeader.Index / HASHSLICESIZE].SlicePtr,
  720.                                     &((*HashTableHandle)[BlockHeader.Index / HASHSLICESIZE].SlicePtr[
  721.                                     BlockHeader.Index & HASHSLICEMASK]),sizeof(HashSliceEntry));
  722.                                 (*HashTableHandle)[BlockHeader.Index / HASHSLICESIZE].SlicePtr[
  723.                                     BlockHeader.Index & HASHSLICEMASK].FileOffset = TargetScan;
  724.                             }
  725.                         SourceScan += BlockHeader.Size;
  726.                         TargetScan += BlockHeader.Size;
  727.                     }
  728.             }
  729.         FreeSpace = 0;
  730.         TotalSize = TargetScan; /* new end of file */
  731.         FSetEOF(FileReference,TotalSize);
  732.         FSetFilePos(FileReference,TOTALSIZEOFFSET);
  733.         FWriteBlock(FileReference,(char*)&TargetScan,sizeof(long));
  734.         FSetFilePos(FileReference,NUMBLOCKSOFFSET);
  735.         BlockCountTemp = NumBlocks;
  736.         FWriteBlock(FileReference,(char*)&BlockCountTemp,sizeof(long));
  737.         delete FreeBlockList;
  738.         Temp = new CSack;
  739.         Temp->ISack(sizeof(FreeBlockEntry),FREEBLOCKNODESIZE);
  740.         FreeBlockList = Temp;
  741.         FileModified = True;
  742.         FlushVitalStats();
  743.         EndTimeConsumingOperation();
  744.     }
  745.  
  746.  
  747. /* save all data to disk & remove it from memory */
  748. void                    CDiskCache::FlushAll(MyBoolean FlushHeldBlocks)
  749.     {
  750.         register long            Scan;
  751.         register short        Index;
  752.  
  753.         ERROR(!DiskCacheInitFlag,
  754.             PRERR(ForceAbort,"CDiskCache::FlushAll called on uninitialized object."));
  755.         StartTimeConsumingOperation();
  756.         for (Scan = HandleSize((Handle)HashTableHandle) / sizeof(HashTableEntry) - 1;
  757.             Scan >= 0; Scan -= 1)
  758.             {
  759.                 for (Index = HASHSLICESIZE - 1; Index >= 0; Index -= 1)
  760.                     {
  761.                         register HashSliceEntry*    HSE;
  762.  
  763.                         CheckCursor();
  764.                         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Scan]),sizeof(HashTableEntry));
  765.                         PRNGCHK((*HashTableHandle)[Scan].SlicePtr,&((*HashTableHandle)[Scan]
  766.                             .SlicePtr[Index]),sizeof(HashSliceEntry));
  767.                         HSE = &((*HashTableHandle)[Scan].SlicePtr[Index]);
  768.                         if (FlushHeldBlocks)
  769.                             {
  770.                                 HSE->NoPurge = False; /* force it to flush */
  771.                             }
  772.                         SwapBlockOut(HSE);
  773.                     }
  774.             }
  775.         FlushVitalStats();
  776.         EndTimeConsumingOperation();
  777.     }
  778.  
  779.  
  780. /* save all data to disk, but keep it in memory */
  781. void                    CDiskCache::SaveAll(void)
  782.     {
  783.         register long            Scan;
  784.         register short        Index;
  785.  
  786.         ERROR(!DiskCacheInitFlag,
  787.             PRERR(ForceAbort,"CDiskCache::SaveAll called on uninitialized object."));
  788.         StartTimeConsumingOperation();
  789.         for (Scan = HandleSize((Handle)HashTableHandle) / sizeof(HashTableEntry) - 1;
  790.             Scan >= 0; Scan -= 1)
  791.             {
  792.                 for (Index = HASHSLICESIZE - 1; Index >= 0; Index -= 1)
  793.                     {
  794.                         CheckCursor();
  795.                         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Scan]),sizeof(HashTableEntry));
  796.                         PRNGCHK((*HashTableHandle)[Scan].SlicePtr,&((*HashTableHandle)[Scan]
  797.                             .SlicePtr[Index]),sizeof(HashSliceEntry));
  798.                         SaveBlock(&((*HashTableHandle)[Scan].SlicePtr[Index]));
  799.                     }
  800.             }
  801.         FlushVitalStats();
  802.         EndTimeConsumingOperation();
  803.     }
  804.  
  805.  
  806. /* find an unallocated hash entry */
  807. IndexType            CDiskCache::FindFreeHashEntry(void)
  808.     {
  809.         register HashSliceEntry*    Temp;
  810.         MyBoolean                                    Succeeded;
  811.  
  812.         if (FreeHashEntry == NIL)
  813.             {
  814.                 /* create another one */
  815.                 Succeeded = ValidateHashEntry(HASHSLICESIZE / sizeof(HashTableEntry)
  816.                     * HandleSize((Handle)HashTableHandle)); /* add new row */
  817.                 if (!Succeeded)
  818.                     {
  819.                         return UnallocatedIndex; /* allocation failed */
  820.                     }
  821.             }
  822.         /* return an existing one */
  823.         Temp = FreeHashEntry;
  824.         FreeHashEntry = (HashSliceEntry*)(Temp->HandleToData); /* delink entry */
  825.         return Temp->FileOffset; /* return index */
  826.     }
  827.  
  828.  
  829. /* make sure the hash entry exists */
  830. MyBoolean            CDiskCache::ValidateHashEntry(register IndexType Index)
  831.     {
  832.         register long                            HashTableScan;
  833.         register long                            LastHashEntry;
  834.         register short                        SliceScan;
  835.         HashTableEntry**                    Temp;
  836.         register HashSliceEntry*    HSE;
  837.  
  838.         while ((Index/HASHSLICESIZE) >= (HandleSize((Handle)HashTableHandle)
  839.             / sizeof(HashTableEntry)) )
  840.             {
  841.                 Temp = (HashTableEntry**)AllocHandle(HandleSize((Handle)HashTableHandle)
  842.                     + sizeof(HashTableEntry));
  843.                 SetTag(Temp,"CDC HashTable");
  844.                 LastHashEntry = HandleSize((Handle)HashTableHandle) / sizeof(HashTableEntry);
  845.                 for (HashTableScan = LastHashEntry - 1; HashTableScan >= 0; HashTableScan -= 1)
  846.                     {
  847.                         HRNGCHK(HashTableHandle,&((*HashTableHandle)[HashTableScan]),
  848.                             sizeof(HashTableEntry));
  849.                         HRNGCHK(Temp,&((*Temp)[HashTableScan]),sizeof(HashTableEntry));
  850.                         (*Temp)[HashTableScan] = (*HashTableHandle)[HashTableScan];
  851.                     }
  852.                 HRNGCHK(Temp,&((*Temp)[LastHashEntry].SlicePtr),sizeof(HashTableEntry));
  853.                 (*Temp)[LastHashEntry].SlicePtr =
  854.                     (HashSliceEntry*)AllocPtrCanFail(HASHSLICESIZE*sizeof(HashSliceEntry));
  855.                 if ((*Temp)[LastHashEntry].SlicePtr == NIL)
  856.                     {
  857.                         ReleaseHandle((Handle)Temp); /* couldn't allocate new entry, so return */
  858.                         return False; /* allocation failed */
  859.                     }
  860.                 ReleaseHandle((Handle)HashTableHandle);
  861.                 HashTableHandle = Temp;
  862.                 SetTag((*Temp)[LastHashEntry].SlicePtr,"CDC Slice");
  863.                 HSE = &((*Temp)[LastHashEntry].SlicePtr[HASHSLICESIZE]);
  864.                 for (SliceScan = HASHSLICESIZE - 1; SliceScan >= 0; SliceScan -= 1)
  865.                     {
  866.                         /* this is the same operation ReleaseHashEntry performs */
  867.                         HSE -= 1; /* decrement to previous HashSliceEntry */
  868.                         PRNGCHK((*Temp)[LastHashEntry].SlicePtr,HSE,sizeof(HashSliceEntry));
  869.                         HSE->UsedCell = False;
  870.                         /* linking this to the free list */
  871.                         HSE->HandleToData = (char**)FreeHashEntry;
  872.                         FreeHashEntry = HSE; /* now its been pushed into list */
  873.                         HSE->FileOffset = (LastHashEntry * HASHSLICESIZE)
  874.                             + SliceScan; /* store index for easy reference */
  875.                     }
  876.                 MaxIndex = (LastHashEntry + 1) * HASHSLICESIZE;
  877.                 FileModified = True;
  878.                 FlushVitalStats();
  879.             }
  880.         return True;
  881.     }
  882.  
  883.  
  884. /* make a hash entry once again available */
  885. void                    CDiskCache::ReleaseHashEntry(register IndexType Index)
  886.     {
  887.         register HashSliceEntry*    HSE;
  888.  
  889.         ERROR((Index/HASHSLICESIZE) >= (HandleSize((Handle)HashTableHandle)
  890.             / sizeof(HashTableEntry)),PRERR(ForceAbort,
  891.             "CDiskCache::ReleaseHashEntry called on undefined index."));
  892.         HRNGCHK(HashTableHandle,&((*HashTableHandle)[Index / HASHSLICESIZE]),
  893.             sizeof(HashTableEntry));
  894.         HSE = &((*HashTableHandle)[Index / HASHSLICESIZE].SlicePtr[Index & HASHSLICEMASK]);
  895.         PRNGCHK((*HashTableHandle)[Index / HASHSLICESIZE].SlicePtr,HSE,
  896.             sizeof(HashSliceEntry));
  897.         ERROR((HSE->UsedCell)==False,PRERR(ForceAbort,
  898.             "CDiskCache::ReleaseHashEntry was called on an index that was already released."));
  899.         HSE->UsedCell = False;
  900.         HSE->HandleToData = (char**)FreeHashEntry; /* linking this to free linked list */
  901.         FreeHashEntry = HSE; /* now its pushed into list */
  902.         HSE->FileOffset = Index; /* store index in for easy reference */
  903.     }
  904.  
  905.  
  906. /* allocate a block of data in the disk file somewhere */
  907. MyBoolean            CDiskCache::AllocateDiskBlock(long NumBytes, long* IndexToBlockStart,
  908.                                 IndexType Index)
  909.     {
  910.         FileBlockNode        BlockHeader;
  911.         FreeBlockEntry    FreeBlock;
  912.  
  913.         /* looking for block somewhere */
  914.         FreeBlockList->ResetScan();
  915.         while (FreeBlockList->GetNext(&FreeBlock))
  916.             {
  917.                 if (FreeBlock.Size >= NumBytes + sizeof(FileBlockNode))
  918.                     {
  919.                         /* found one! */
  920.                         FreeBlockList->KillElement(&FreeBlock); /* remove from free list */
  921.                         *IndexToBlockStart = FreeBlock.FileOffset;
  922.                         if (FreeBlock.Size - (NumBytes + sizeof(FileBlockNode))
  923.                             >= sizeof(FileBlockNode) + sizeof(long))
  924.                             {
  925.                                 /* we'll have to split the block, since there's enough room to hold */
  926.                                 /* another block header AND a long int */
  927.                                 /* writing first block header */
  928.                                 NumBlocks += 1; /* by splitting, we increase the number of blocks */
  929.                                 BlockHeader.Index = Index;
  930.                                 BlockHeader.Size = NumBytes + sizeof(BlockHeader);
  931.                                 BlockHeader.Correction = 0;
  932.                                 FreeSpace -= BlockHeader.Size; /* remove this from freespace pool */
  933.                                 FSetFilePos(FileReference,FreeBlock.FileOffset);
  934.                                 ERROR(FLastError()!=noErr,PRERR(AllowResume,
  935.                                     "FSetFilePos error occurred in CDiskCache::AllocateDiskBlock."));
  936.                                 FWriteBlock(FileReference,(char*)&BlockHeader,sizeof(FileBlockNode));
  937.                                 ERROR(FLastError()!=noErr,PRERR(AllowResume,
  938.                                     "FWriteBlock error occurred in CDiskCache::AllocateDiskBlock."));
  939.                                 /* making second block header */
  940.                                 FreeBlock.FileOffset += BlockHeader.Size; /* right after first block */
  941.                                 FreeBlock.Size -= BlockHeader.Size; /* it's the left over space */
  942.                                 FreeBlockList->PushElement(&FreeBlock); /* add to the free world */
  943.                                 BlockHeader.Size = FreeBlock.Size;
  944.                                 BlockHeader.Index = UnallocatedIndex;
  945.                                 BlockHeader.Correction = 0;
  946.                                 FSetFilePos(FileReference,FreeBlock.FileOffset);
  947.                                 ERROR(FLastError()!=noErr,PRERR(AllowResume,
  948.                                     "FSetFilePos error occurred in CDiskCache::AllocateDiskBlock."));
  949.                                 FWriteBlock(FileReference,(char*)&BlockHeader,sizeof(FileBlockNode));
  950.                                 ERROR(FLastError()!=noErr,PRERR(AllowResume,
  951.                                     "FWriteBlock error occurred in CDiskCache::AllocateDiskBlock."));
  952.                             }
  953.                          else
  954.                             {
  955.                                 /* we'll have to allocate the whole block */
  956.                                 /* BlockHeader.Size stays the same */
  957.                                 BlockHeader.Size = FreeBlock.Size;
  958.                                 BlockHeader.Index = Index;
  959.                                 BlockHeader.Correction = FreeBlock.Size - sizeof(FileBlockNode)
  960.                                     - NumBytes;
  961.                                 FreeSpace -= FreeBlock.Size; /* remove from freespace pool */
  962.                                 FSetFilePos(FileReference,FreeBlock.FileOffset);
  963.                                 ERROR(FLastError()!=noErr,PRERR(AllowResume,
  964.                                     "FSetFilePos error occurred in CDiskCache::AllocateDiskBlock."));
  965.                                 FWriteBlock(FileReference,(char*)&BlockHeader,sizeof(FileBlockNode));
  966.                                 ERROR(FLastError()!=noErr,PRERR(AllowResume,
  967.                                     "FWriteBlock error occurred in CDiskCache::AllocateDiskBlock."));
  968.                             }
  969.                         return True; /* and we're outa here */
  970.                     }
  971.             }
  972.         /* end of file reached, with no suitable blocks:  so append, my friend */
  973.         NumBlocks += 1; /* by appending, we add a block */
  974.         FSetEOF(FileReference,TotalSize + sizeof(FileBlockNode) + NumBytes);
  975.         if (FLastError() != noErr)
  976.             {
  977.                 FSetEOF(FileReference,TotalSize); /* undo the change */
  978.                 CompactFile(); /* try to squeeze out free space */
  979.                 FSetEOF(FileReference,TotalSize + sizeof(FileBlockNode) + NumBytes);
  980.                 if (FLastError() != noErr)
  981.                     {
  982.                         FSetEOF(FileReference,TotalSize); /* undo again, since it still failed */
  983.                         EXECUTE(PRERR(AllowResume,
  984.                             "CDiskCache::AllocatediskBlock unable to allocate disk space."));
  985.                         return False; /* indicate to your superiors that you're incompetent */
  986.                     }
  987.             }
  988.         /* writing the block header */
  989.         BlockHeader.Size = sizeof(FileBlockNode) + NumBytes;
  990.         BlockHeader.Correction = 0;
  991.         BlockHeader.Index = Index;
  992.         FSetFilePos(FileReference,TotalSize);
  993.         ERROR(FLastError()!=noErr,PRERR(AllowResume,
  994.             "FSetFilePos error occurred in CDiskCache::AllocateDiskBlock."));
  995.         *IndexToBlockStart = TotalSize; /* record start of block */
  996.         FWriteBlock(FileReference,(char*)&BlockHeader,sizeof(FileBlockNode));
  997.         ERROR(FLastError()!=noErr,PRERR(AllowResume,
  998.             "FWriteBlock error occurred in CDiskCache::AllocateDiskBlock."));
  999.         /* adjusting our and the file's total size count */
  1000.         TotalSize += BlockHeader.Size;
  1001.         FileModified = True;
  1002.         FlushVitalStats();
  1003.         return True;
  1004.     }
  1005.  
  1006.  
  1007. /* unallocate a block of data in the disk file somewhere, try to combine it */
  1008. /* with any neighboring blocks */
  1009. void                CDiskCache::ReleaseDiskBlock(long IndexToBlockStart)
  1010.     {
  1011.         FileBlockNode        BlockHeader;
  1012.         FreeBlockEntry    FreeBlock;
  1013.         short                        Error;
  1014.  
  1015.         /* getting info on our block */
  1016.      GetInfoPoint:
  1017.         FSetFilePos(FileReference,IndexToBlockStart);
  1018.         FReadBlock(FileReference,(char*)&BlockHeader,sizeof(FileBlockNode));
  1019.         BlockHeader.Index = UnallocatedIndex; /* set it as unallocated */
  1020.         /* note: we'll be writing the BlockHeader back at the end */
  1021.         /* seeing if we can combine left */
  1022.      CombLeftPoint:
  1023.         FreeBlockList->ResetScan();
  1024.         while (FreeBlockList->GetNext(&FreeBlock))
  1025.             {
  1026.                 if (FreeBlock.FileOffset + FreeBlock.Size == IndexToBlockStart)
  1027.                     {
  1028.                         /* we can combine left! */
  1029.                         FreeBlockList->KillElement(&FreeBlock); /* get rid of old */
  1030.                         /* move block start to beginning of this new left block: */
  1031.                         FreeSpace -= FreeBlock.Size;
  1032.                         IndexToBlockStart -= FreeBlock.Size;
  1033.                         BlockHeader.Size += FreeBlock.Size; /* size contains both */
  1034.                         NumBlocks -= 1; /* we're combining two, so one disappears */
  1035.                         goto CombRightPoint;
  1036.                     }
  1037.             }
  1038.         /* seeing if we can combine right */
  1039.      CombRightPoint:
  1040.         FreeBlockList->ResetScan();
  1041.         while (FreeBlockList->GetNext(&FreeBlock))
  1042.             {
  1043.                 if (BlockHeader.Size + IndexToBlockStart == FreeBlock.FileOffset)
  1044.                     {
  1045.                         /* we can combine right! */
  1046.                         FreeBlockList->KillElement(&FreeBlock); /* get rid of old */
  1047.                         FreeSpace -= FreeBlock.Size;
  1048.                         BlockHeader.Size += FreeBlock.Size; /* increase the size */
  1049.                         NumBlocks -= 1; /* again, two combine into one */
  1050.                         goto RewritePoint;
  1051.                     }
  1052.             }
  1053.         /* writing data back */
  1054.      RewritePoint:
  1055.         /* update our internal free block list */
  1056.         FreeBlock.Size = BlockHeader.Size;
  1057.         FreeBlock.FileOffset = IndexToBlockStart;
  1058.         FreeBlockList->PushElement(&FreeBlock);
  1059.         /* udpate disk file */
  1060.         Error = FSetFilePos(FileReference,IndexToBlockStart);
  1061.         ERROR(Error!=noErr,PRERR(ForceAbort,
  1062.             "CDiskCache::ReleaseDiskBlock couldn't set file position."));
  1063.         Error = FWriteBlock(FileReference,(char*)&BlockHeader,sizeof(FileBlockNode));
  1064.         ERROR(Error!=noErr,PRERR(ForceAbort,
  1065.             "CDiskCache::ReleaseDiskBlock couldn't write block header back."));
  1066.         /* update internal count of free space */
  1067.         FreeSpace += BlockHeader.Size;
  1068.         FileModified = True;
  1069.         FlushVitalStats();
  1070.     }
  1071.  
  1072.  
  1073. /* save a block out to disk */
  1074. void                    CDiskCache::SaveBlock(HashSliceEntry* BlockInfo)
  1075.     {
  1076.         char        HandleStat;
  1077.         short        Error;
  1078.         EXECUTE(FileBlockNode BlockHeader;)
  1079.         EXECUTE(long HandleSizeTemp;)
  1080.  
  1081.         if (BlockInfo->UsedCell)
  1082.             {
  1083.                 if ((BlockInfo->HandleToData != NIL) && (*(BlockInfo->HandleToData) == NIL))
  1084.                     {
  1085.                         /* it was purged by the memory manager */
  1086.                         ReleaseHandle(BlockInfo->HandleToData);
  1087.                         BlockInfo->HandleToData = NIL;
  1088.                     }
  1089.                 if (BlockInfo->Changed && (BlockInfo->HandleToData != NIL))
  1090.                     {
  1091.                         EXECUTE(FSetFilePos(FileReference,BlockInfo->FileOffset));
  1092.                         EXECUTE(FReadBlock(FileReference,(char*)&BlockHeader,sizeof(FileBlockNode)));
  1093.                         EXECUTE(HandleSizeTemp = HandleSize(BlockInfo->HandleToData));
  1094.                         ERROR(BlockHeader.Size - BlockHeader.Correction - sizeof(FileBlockNode)
  1095.                             != HandleSizeTemp,PRERR(ForceAbort,
  1096.                             "CDiskCache::SaveBlock block size on disk doesn't match handle size."));
  1097.                         Error = FSetFilePos(FileReference,BlockInfo->FileOffset
  1098.                             + sizeof(FileBlockNode));
  1099.                         ERROR(Error!=noErr,PRERR(AllowResume,
  1100.                             "CDiskCache::SaveBlock unable to set file position."));
  1101.                         if (Error == noErr)
  1102.                             {
  1103.                                 HandleStat = HGetState(BlockInfo->HandleToData);
  1104.                                 HLock(BlockInfo->HandleToData);
  1105.                                 Error = FWriteBlock(FileReference,*(BlockInfo->HandleToData),
  1106.                                     HandleSize(BlockInfo->HandleToData));
  1107.                                 ERROR(Error!=noErr,PRERR(AllowResume,
  1108.                                     "CDiskCache::SaveBlock unable to write block to disk."));
  1109.                                 HSetState(BlockInfo->HandleToData,HandleStat);
  1110.                                 if (Error == noErr)
  1111.                                     {
  1112.                                         BlockInfo->Changed = False;
  1113.                                     }
  1114.                                 FileModified = True;
  1115.                             }
  1116.                     }
  1117.             }
  1118.     }
  1119.  
  1120.  
  1121. /* save a block out to disk and dispose of its loaded image */
  1122. long                    CDiskCache::SwapBlockOut(HashSliceEntry* BlockInfo)
  1123.     {
  1124.         long        SizeTemp;
  1125.  
  1126.         SizeTemp = 0;
  1127.         if (BlockInfo->UsedCell)
  1128.             {
  1129.                 SaveBlock(BlockInfo);
  1130.                 if ((BlockInfo->HandleToData != NIL) && (BlockInfo->NoPurge == False)
  1131.                     && (BlockInfo->Changed == False))
  1132.                     {
  1133.                         SizeTemp = HandleSize(BlockInfo->HandleToData);
  1134.                         ReleaseHandle(BlockInfo->HandleToData);
  1135.                         BlockInfo->HandleToData = NIL;
  1136.                     }
  1137.             }
  1138.         return SizeTemp;
  1139.     }
  1140.  
  1141.  
  1142. MyBoolean            CDiskCache::SwapBlockIn(HashSliceEntry* BlockInfo)
  1143.     {
  1144.         FileBlockNode        BlockHeader;
  1145.  
  1146.         ERROR(!BlockInfo->UsedCell,PRERR(ForceAbort,
  1147.             "CDiskCache::SwapBlockIn called on unallocated block."));
  1148.         if (BlockInfo->HandleToData != NIL)
  1149.             {
  1150.                 if (*(BlockInfo->HandleToData) != NIL)
  1151.                     {
  1152.                         if (BlockInfo->NoPurge || BlockInfo->Changed)
  1153.                             {
  1154.                                 HNoPurge(BlockInfo->HandleToData);
  1155.                             }
  1156.                          else
  1157.                             {
  1158.                                 HPurge(BlockInfo->HandleToData);
  1159.                             }
  1160.                         return True;
  1161.                     }
  1162.                  else
  1163.                     {
  1164.                         ReleaseHandle(BlockInfo->HandleToData);
  1165.                     }
  1166.             }
  1167.         FSetFilePos(FileReference,BlockInfo->FileOffset);
  1168.         FReadBlock(FileReference,(char*)&BlockHeader,sizeof(FileBlockNode));
  1169.         BlockInfo->HandleToData = AllocHandleCanFail(BlockHeader.Size
  1170.             - BlockHeader.Correction - sizeof(FileBlockNode));
  1171.         if (BlockInfo->HandleToData != NIL)
  1172.             {
  1173.                 SetDiskTag(BlockInfo->HandleToData,Trandle2Index(&(BlockInfo->HandleToData)));
  1174.                 HLock(BlockInfo->HandleToData);
  1175.                 FReadBlock(FileReference,*(BlockInfo->HandleToData),
  1176.                     HandleSize(BlockInfo->HandleToData));
  1177.                 HUnlock(BlockInfo->HandleToData);
  1178.                 BlockInfo->Changed = False;
  1179.                 if (!BlockInfo->NoPurge)
  1180.                     {
  1181.                         HPurge(BlockInfo->HandleToData);
  1182.                     }
  1183.                 return True;
  1184.             }
  1185.         return False;
  1186.     }
  1187.  
  1188.  
  1189. /* rewrite TotalSize, NumBlocks, and MaxIndex back to file */
  1190. void                    CDiskCache::FlushVitalStats(void)
  1191.     {
  1192.         OSErr        Error;
  1193.         long        Temp;
  1194.  
  1195.         Error = FSetFilePos(FileReference,TOTALSIZEOFFSET);
  1196.         Temp = TotalSize;
  1197.         Error = FWriteBlock(FileReference,(char*)&Temp,sizeof(long));
  1198.  
  1199.         Error = FSetFilePos(FileReference,NUMBLOCKSOFFSET);
  1200.         Temp = NumBlocks;
  1201.         Error = FWriteBlock(FileReference,(char*)&Temp,sizeof(long));
  1202.  
  1203.         Error = FSetFilePos(FileReference,MAXINDEXOFFSET);
  1204.         Temp = MaxIndex;
  1205.         Error = FWriteBlock(FileReference,(char*)&Temp,sizeof(long));
  1206.     }
  1207.  
  1208.  
  1209. void                CDiskCache::DoIdle(long TimeSinceLastEvent)
  1210.     {
  1211.         if (FileModified && ((ulong)(TickCount() - TimeSinceLastEvent) > FILEFLUSHTHRESHHOLD))
  1212.             {
  1213.                 FFlushFile(FileReference);
  1214.                 FileModified = False;
  1215.             }
  1216.     }
  1217.  
  1218.  
  1219. /* start at StartingIndex and purge blocks to the end until */
  1220. /* SizeNeeded bytes are released */
  1221. long                    CDiskCache::PurgeSome(long SizeNeeded, IndexType* StartingIndex)
  1222.     {
  1223.         long        Accr;
  1224.  
  1225.         Accr = 0;
  1226.         while (((*StartingIndex) < MaxIndex) && (SizeNeeded / 4 > Accr))
  1227.             {
  1228.                 HRNGCHK(HashTableHandle,&((*HashTableHandle)[(*StartingIndex)/HASHSLICESIZE]),
  1229.                     sizeof(HashTableEntry));
  1230.                 PRNGCHK((*HashTableHandle)[(*StartingIndex)/HASHSLICESIZE].SlicePtr,
  1231.                     &((*HashTableHandle)[(*StartingIndex)/HASHSLICESIZE].
  1232.                     SlicePtr[(*StartingIndex) & HASHSLICEMASK]),sizeof(HashSliceEntry));
  1233.                 Accr += SwapBlockOut(&((*HashTableHandle)[(*StartingIndex)/HASHSLICESIZE].
  1234.                     SlicePtr[(*StartingIndex) & HASHSLICEMASK]));
  1235.                 (*StartingIndex) += 1;
  1236.             }
  1237.         return Accr;
  1238.     }
  1239.  
  1240.  
  1241. /* verify file integrity and load index nodes.  True = successful, False = bad file */
  1242. /* any existing index nodes are assumed to be invalid, so call only at start */
  1243. MyBoolean            CDiskCache::InitAll(void)
  1244.     {
  1245.         long        FileSize;
  1246.  
  1247.         if (FGetEOF(FileReference,&FileSize) != noErr)
  1248.             {
  1249.                 return False;
  1250.             }
  1251.         if (FileSize == 0)
  1252.             {
  1253.                 long        Temp;
  1254.  
  1255.                 TotalSize = DATASTARTOFFSET;
  1256.                 NumBlocks = 0;
  1257.                 Temp = DATASTARTOFFSET;
  1258.                 FSetFilePos(FileReference,TOTALSIZEOFFSET);
  1259.                 FWriteBlock(FileReference,(char*)&Temp,sizeof(long));
  1260.                 Temp = UnallocatedIndex;
  1261.                 FSetFilePos(FileReference,SPECIALINDEXOFFSET);
  1262.                 FWriteBlock(FileReference,(char*)&Temp,sizeof(long));
  1263.                 Temp = 0;
  1264.                 FSetFilePos(FileReference,NUMBLOCKSOFFSET);
  1265.                 FWriteBlock(FileReference,(char*)&Temp,sizeof(long));
  1266.                 return True;
  1267.             }
  1268.          else
  1269.             {
  1270.                 FileBlockNode        Scan;
  1271.                 long                        FilePos;
  1272.                 long                        TotalSizeTemp;
  1273.                 long                        NumBlocksTemp;
  1274.                 long                        MaxIndexTemp;
  1275.                 long                        HashTableScan;
  1276.                 long                        SliceScan;
  1277.  
  1278.                 FSetFilePos(FileReference,TOTALSIZEOFFSET);
  1279.                 FReadBlock(FileReference,(char*)&TotalSizeTemp,sizeof(long)); /* get file size */
  1280.                 if (FileSize < TotalSizeTemp)
  1281.                     {
  1282.                         return False; /* if not equal, then file is corrupt */
  1283.                         /* if >, then maybe the EOF was lost.  We'll keep checking */
  1284.                     }
  1285.                 FSetFilePos(FileReference,NUMBLOCKSOFFSET);
  1286.                 FReadBlock(FileReference,(char*)&NumBlocksTemp,sizeof(long));
  1287.                 FSetFilePos(FileReference,MAXINDEXOFFSET);
  1288.                 FReadBlock(FileReference,(char*)&MaxIndexTemp,sizeof(long));
  1289.                 NumBlocks = 0;
  1290.                 TotalSize = TotalSizeTemp;
  1291.                 MaxIndex = MaxIndexTemp;
  1292.                 FilePos = DATASTARTOFFSET;
  1293.                 FreeSpace = 0;
  1294.                 /* creating all hash table entries and setting them as unused */
  1295.                 ReleaseHandle((Handle)HashTableHandle);
  1296.                 HashTableHandle = (HashTableEntry**)
  1297.                     AllocHandle(sizeof(HashTableEntry**) * (MaxIndexTemp / HASHSLICESIZE));
  1298.                 for (HashTableScan = (MaxIndexTemp / HASHSLICESIZE) - 1; HashTableScan >= 0;
  1299.                     HashTableScan -= 1)
  1300.                     {
  1301.                         (*HashTableHandle)[HashTableScan].SlicePtr =
  1302.                             (HashSliceEntry*)AllocPtr(sizeof(HashSliceEntry) * HASHSLICESIZE);
  1303.                         for (SliceScan = HASHSLICESIZE - 1; SliceScan >= 0; SliceScan -= 1)
  1304.                             {
  1305.                                 (*HashTableHandle)[HashTableScan].SlicePtr[SliceScan].UsedCell = False;
  1306.                             }
  1307.                     }
  1308.                 /* accounting for all blocks in the file */
  1309.                 while (FilePos < TotalSizeTemp)
  1310.                     {
  1311.                         /* getting block from disk */
  1312.                         FSetFilePos(FileReference,FilePos);
  1313.                         FReadBlock(FileReference,(char*)&Scan,sizeof(FileBlockNode));
  1314.                         /* creating index entry */
  1315.                         NumBlocks += 1; /* count this block in the total */
  1316.                         if (Scan.Index != UnallocatedIndex)
  1317.                             {
  1318.                                 register HashSliceEntry*    HSE;
  1319.  
  1320.                                 if (Scan.Index >= MaxIndexTemp)
  1321.                                     {
  1322.                                         return False; /* index exceeded, file is corrupt */
  1323.                                     }
  1324.                                 HSE = &((*HashTableHandle)[Scan.Index / HASHSLICESIZE]
  1325.                                     .SlicePtr[Scan.Index & HASHSLICEMASK]);
  1326.                                 HSE->HandleToData = NIL;
  1327.                                 HSE->FileOffset = FilePos;
  1328.                                 HSE->UsedCell = True;
  1329.                                 HSE->Changed = False;
  1330.                                 HSE->NoPurge = False;
  1331.                             }
  1332.                          else
  1333.                             {
  1334.                                 FreeBlockEntry    Free;
  1335.  
  1336.                                 Free.Size = Scan.Size;
  1337.                                 Free.FileOffset = FilePos;
  1338.                                 FreeBlockList->PushElement(&Free);
  1339.                                 FreeSpace += Scan.Size;
  1340.                             }
  1341.                         /* stepping to next block */
  1342.                         FilePos += Scan.Size;
  1343.                     }
  1344.                 /* adding unused blocks to the free block list */
  1345.                 for (HashTableScan = (MaxIndexTemp / HASHSLICESIZE) - 1; HashTableScan >= 0;
  1346.                     HashTableScan -= 1)
  1347.                     {
  1348.                         for (SliceScan = HASHSLICESIZE - 1; SliceScan >= 0; SliceScan -= 1)
  1349.                             {
  1350.                                 HashSliceEntry*    HSE;
  1351.  
  1352.                                 /* this is similar to the procedure used by ReleaseHashEntry */
  1353.                                 HSE = &((*HashTableHandle)[HashTableScan].SlicePtr[SliceScan]);
  1354.                                 if (!(HSE->UsedCell))
  1355.                                     {
  1356.                                         HSE->HandleToData =
  1357.                                             (char**)FreeHashEntry; /* linking this to free linked list */
  1358.                                         FreeHashEntry = HSE; /* now its pushed into list */
  1359.                                         HSE->FileOffset = (HashTableScan * HASHSLICESIZE)
  1360.                                             + SliceScan; /* store index in for quick reference */
  1361.                                     }
  1362.                             }
  1363.                     }
  1364.                 if (NumBlocks != NumBlocksTemp)    
  1365.                     {
  1366.                         return False; /* this is really bad */
  1367.                     }
  1368.                 return True; /* assume its ok */
  1369.             }
  1370.     }
  1371.  
  1372.  
  1373. /* the master grow zone function calls this to dispose of blocks from CDiskCaches */
  1374. static long        CDiskCache::MyGrowZone(ulong SizeNeeded)
  1375.     {
  1376.         long                Accr;
  1377.         CDiskCache*    StartingCache;
  1378.  
  1379.         if (LastCachePurged == NIL)
  1380.             {
  1381.                 return 0; /* failed, since there are no caches */
  1382.             }
  1383.         Accr = 0;
  1384.         StartingCache = LastCachePurged;
  1385.         do
  1386.             {
  1387.                 Accr += LastCachePurged->PurgeSome(SizeNeeded - Accr,&LastIndexPurged);
  1388.                 if (Accr < SizeNeeded)
  1389.                     {
  1390.                         LastIndexPurged = 0;
  1391.                         LastCachePurged = LastCachePurged->NextCache;
  1392.                     }
  1393.             } while ((LastCachePurged != StartingCache) && (Accr < SizeNeeded));
  1394.         return Accr;
  1395.     }
  1396.  
  1397.  
  1398. /* when a system error occurs, call this to flush all existing caches and close them, */
  1399. /* so that the user might recover data later. */
  1400. void        SystemErrorFlushAllDiskCaches(void)
  1401.     {
  1402.         CDiskCache*    StartingCache;
  1403.         CDiskCache*    Scan;
  1404.  
  1405.         if (LastCachePurged != NIL)
  1406.             {
  1407.                 Scan = LastCachePurged;
  1408.                 StartingCache = LastCachePurged;
  1409.                 do
  1410.                     {
  1411.                         Scan->FlushAll(DoFlushHeldBlocks);
  1412.                         Scan = Scan->NextCache;
  1413.                     } while (Scan != StartingCache);
  1414.             }
  1415.     }
  1416.